<?php

namespace app\common\service\auth;

use app\common\exception\ApiErrDesc;
use app\common\exception\ApiException;
use Firebase\JWT\BeforeValidException;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\SignatureInvalidException;
use think\Exception;


/**
 * Jwt令牌
 * https://github.com/firebase/php-jwt
 *
 * Class JwtService
 * @package app\common\service\auth
 * @author ^2_3^王尔贝
 */
class JwtService
{

    /**
     * 实例
     * @var
     * @author ^2_3^王尔贝
     */
    private static $instance;

    /**
     * 令牌类型
     * @var string admin|user
     * @author ^2_3^王尔贝
     */
    public $tokenType = 'admin';

    /**
     * 令牌
     * @var
     * @author ^2_3^王尔贝
     */
    public $token;

    /**
     * 解析令牌对象
     * @var
     * @author ^2_3^王尔贝
     */
    public $decodeToken;

    /**
     * jwt签发者
     * @var string
     * @author ^2_3^王尔贝
     */
    public $iss = '';

    /**
     * 接收jwt方
     * @var string
     * @author ^2_3^王尔贝
     */
    public $aud = '';

    /**
     *  jwt所面向的用户
     * @var string
     * @author ^2_3^王尔贝
     */
    public $sub = '';

    /**
     * jwt过期时间(过期时间必须要大于签发时间)
     * @var int
     * @author ^2_3^王尔贝
     */
    public $exp = 60 * 60 * 2;

    /**
     * 有效期(定义在什么时间之前，该jwt都是不可用的)
     * @var int
     * @author ^2_3^王尔贝
     */
    public $nbf = 0;

    /**
     * jwt签发时间
     * @var int
     * @author ^2_3^王尔贝
     */
    public $iat = 0;

    /**
     *  jwt唯一身份标识(主要用来作为一次性token，从而回避重放攻击)
     * @var string
     * @author ^2_3^王尔贝
     */
    private $jti = '';

    /**
     * jwt key
     * @var string
     * @author ^2_3^王尔贝
     */
    private $jwtKey = '';

    /**
     * 用户id
     * @var int
     * @author ^2_3^王尔贝
     */
    public $userId = 0;

    /**
     * 管理员id
     * @var int
     * @author ^2_3^王尔贝
     */
    public $adminId = 0;

    /**
     * 邀请码
     * @var string
     * @author ^2_3^王尔贝
     */
    public $inviteCode = '';

    /**
     * 实例
     * @return JwtService
     * @author ^2_3^王尔贝
     */
    public static function getInstance()
    {
        if( !self::$instance ) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    /**
     * 构造器
     * JwtService constructor.
     */
    private function __construct()
    {
        $this->jwtKey = env('jwt.key');
    }

    /**
     * 令牌生成
     * @param int $id
     * @param string $type
     * @return $this
     * @author ^2_3^王尔贝
     */
    public function makeToken($id = 0, $type = 'admin')
    {
        $nowTime = time();
        if( $type == 'admin' ) {
            $this->adminId = $id;

        }else if( $type == 'user' ) {
            $this->userId = $id;
        }

        $payload = array(
            "iss" => $this->iss,
            "aud" => $this->aud,
            "iat" => empty( $this->iat ) ? $nowTime : $this->iat,
            "nbf" => empty( $this->nbf ) ? $nowTime : $this->nbf,
            "exp" => empty( $this->exp ) ? $nowTime + 86400 : $nowTime + $this->exp,
            "type" => $type,
            'id' => $id,
        );

        $this->token = JWT::encode($payload, $this->jwtKey);
        return $this;
    }

    /**
     * 令牌验证
     * @param $token
     * @return $this
     * @throws ApiException
     * @author ^2_3^王尔贝
     */
    public function verifyToken($token)
    {
        try {
            $this->decodeToken = JWT::decode($token, $this->jwtKey, array('HS256'));
            $decodeArray = (array)$this->decodeToken;

            $this->token = $token;
            if( isset( $decodeArray['type'] ) && isset( $decodeArray['id'] ) ) {
                switch ( $decodeArray['type'] ) {
                    case "admin":
                        $this->adminId = $decodeArray['id'];
                        break;
                    case "user_id":
                        $this->userId = $decodeArray['id'];
                        break;
                }
            }

            return $this;

        }catch ( SignatureInvalidException $e ) {
            // token不正确
            throw new ApiException(ApiErrDesc::ERR_TOKEN_WRONG);

        }catch ( BeforeValidException $e ) {
            // token在某个时间点之后才能用
            throw new ApiException(ApiErrDesc::ERR_TOKEN_NO_EFFECT);

        }catch ( ExpiredException $e ) {
            // token过期
            throw new ApiException(ApiErrDesc::ERR_TOKEN_EXPIRED);

        }catch( Exception $e ) {
            // 其他错误
            throw new ApiException(ApiErrDesc::ERR_UNKNOWN);
        }

    }
}